#include "CloudLodController.hxx"
#include "LeafProjector.hxx"

#include "..\AIS_Cloud.hxx"
#include <Standard_Type.hxx>
#include <algorithm>
#include <unordered_set>
#include <cmath>

// ----------- AIS_Cloud 需要暴露的最小接口 ------------
// 1) 获取根节点（没有就提供“顶层节点”），返回 TileLeaf* 列表
// 2) 针对某节点：确保某个 stride 的 Rep 已构建（懒创建）
// 3) 显示/隐藏 “节点+repIdx”
// 4) 查询节点是否叶子、子节点列表、节点已有的 Reps（stride/pointCount/garr）
//

// 1) 返回节点的包围盒
static const Bnd_Box& TL_Box(const ColumnTile& n)
{
	return n.BBox;
}

// 2) 是否叶子：目前是平铺 tiles，就直接返回 true；
// 如果以后支持树结构，再改成 !n.Children.empty()
static bool TL_IsLeaf(const ColumnTile& /*n*/)
{
	return true;
}

// 3) 子节点列表：目前没有层次结构，返回空
static const std::vector<ColumnTile*>& TL_Children(const ColumnTile& /*n*/)
{
	static const std::vector<ColumnTile*> kEmpty;
	return kEmpty;
}

// ----- AIS_Cloud 侧的最小适配 -----
static std::vector<ColumnTile*> Cloud_GetRoots(const Handle(AIS_Cloud)& cloud)
{
	std::vector<ColumnTile*> roots;
	if (cloud.IsNull())
		return roots;

	auto& tiles = cloud->Tiles();
	roots.reserve(tiles.size());
	for (ColumnTile& t : tiles)
	{
		roots.push_back(&t);
	}
	return roots;
}

static void Cloud_BuildRepIfMissing(const Handle(AIS_Cloud)& cloud,
	ColumnTile& node,
	int repIdx)
{
	if (cloud.IsNull())
		return;

	cloud->EnsureTileLODArray(node, repIdx);
}

static void Cloud_ShowNodeRep(const Handle(AIS_Cloud)& cloud,
	ColumnTile& node,
	int repIdx)
{
	if (cloud.IsNull())
		return;

	// 确保该 LOD 有 GArray
	cloud->EnsureTileLODArray(node, repIdx);

	node.Visible = true;
	node.CurrentLOD = repIdx;

	// 标记 AIS_Cloud 需要重算
//	cloud->SetToUpdate();
}

static void Cloud_HideNodeRep(const Handle(AIS_Cloud)& cloud,
	ColumnTile& node,
	int /*repIdx*/)
{
	if (cloud.IsNull())
		return;

	node.Visible = false;
	node.CurrentLOD = -1;

	//	cloud->SetToUpdate();
}

// ----------------- Controller 实现 -----------------

CloudLodController::CloudLodController(const Handle(AIS_InteractiveContext)& ctx,
	const Handle(V3d_View)& view)
	: m_ctx(ctx), m_view(view)
{
}

CloudLodController::~CloudLodController() = default;

void CloudLodController::RegisterCloud(const Handle(AIS_Cloud)& cloud)
{
	CloudEntry e;
	e.cloud = cloud;
	e.roots = Cloud_GetRoots(cloud); // TODO
	m_clouds.push_back(std::move(e));
}

void CloudLodController::UnregisterCloud(const Handle(AIS_Cloud)& cloud)
{
	m_clouds.erase(std::remove_if(m_clouds.begin(), m_clouds.end(),
		[&](const CloudEntry& ce) { return ce.cloud == cloud; }), m_clouds.end());
}

/**
 * @brief 根据屏幕像素大小为tile选择合适的LOD级别
 *
 * @param node 要处理的tile节点
 * @param pixDiag tile在屏幕上的对角线像素大小
 * @param th LOD选择阈值参数
 * @return int 选中的LOD级别索引，如果没有可用的LOD级别返回-1
 *
 * @details
 * 该函数根据tile在屏幕上的像素大小自动选择合适的LOD级别：
 * - 当像素大小小于等于粗粒度阈值时，选择最粗的LOD级别（最大stride）
 * - 当像素大小大于等于细粒度阈值时，选择最细的LOD级别（最小stride）
 * - 在中间范围内，根据像素大小在阈值之间的比例线性插值选择适当的LOD级别
 */
static int chooseRepIdx_(const ColumnTile& node, double pixDiag,
	const CloudLodController::LodThreshold& th)
{
	const auto& reps = TL_Reps(node);
	if (reps.empty()) return -1;

	if (pixDiag <= th.pixDiagCoarse) return (int)reps.size() - 1; // 最粗（stride 最大）
	if (pixDiag >= th.pixDiagFine)   return 0;                  // 最细（stride 最小）

	const double t = (pixDiag - th.pixDiagCoarse) / (th.pixDiagFine - th.pixDiagCoarse);
	int idx = (int)std::round((1.0 - t) * (reps.size() - 1));
	if (idx < 0) idx = 0;
	if (idx > (int)reps.size() - 1) idx = (int)reps.size() - 1;
	return idx;
}

bool CloudLodController::Tick()
{
	auto t0 = clk::now();

	selectLOD_();
	bool anyChanged = applyDiff_();

	m_rt.selectMs = std::chrono::duration<double, std::milli>(
		clk::now() - t0).count();

	// Tick 只做逻辑，不负责 UpdateCurrentViewer
	return anyChanged;
}

//int AIS_Cloud::selectLODForTile_SSE_(const ColumnTile& tile,
//	const Handle(Graphic3d_Camera)& cam,
//	int viewportHeight) const
//{
//	// --- 基本合法性检查 ---
//	if (tile.LODs.empty())
//		return 0;
//
//	// 收集所有有效 LOD 等级（PointCount > 0）
//	std::vector<int> validLevels;
//	validLevels.reserve(tile.LODs.size());
//	for (const auto& l : tile.LODs)
//	{
//		if (l.PointCount > 0)
//			validLevels.push_back(l.Level);
//	}
//	if (validLevels.empty())
//		return 0;
//
//	std::sort(validLevels.begin(), validLevels.end());
//	validLevels.erase(std::unique(validLevels.begin(), validLevels.end()),
//		validLevels.end());
//
//	// --- 计算 tile 的世界尺寸 & 屏幕尺寸 ---
//	const double diagWorld = TileDiagonal(tile);
//	if (diagWorld <= 0.0)
//		return validLevels.front();
//
//	// 用 LeafProjector 把 tile.BBox 投影到屏幕，得到像素级对角线
//	double diagPx = 0.0;
//	if (!myView.IsNull())
//	{
//		diagPx = LeafProjector::PixelDiag(myView, tile.BBox, 0);
//	}
//	else
//	{
//		// 没有 view 的情况下，退化为总是 Level 0
//		return validLevels.front();
//	}
//
//	if (diagPx <= 0.0)
//	{
//		// 太小了，说明 tile 投影不到屏幕上（或只有几像素），
//		// 对于可视性来说，这里直接用最粗一级也可以。
//		return validLevels.back();
//	}
//
//	// --- SSE 选择 ---
//	// 目标：从细到粗遍历，找到“误差像素 <= mySSEThresholdPixels”的最粗 LOD。
//	const double thresholdPx = (mySSEThresholdPixels > 0.0)
//		? mySSEThresholdPixels
//		: 1.5; // 默认
//
//	int   chosenLevel = validLevels.front(); // 默认先用最细
//	bool  foundWithinThresh = false;
//
//	for (int lvl : validLevels)
//	{
//		const TileLODLevel* lod = tile.Level(lvl);
//		if (!lod || lod->PointCount == 0)
//			continue;
//
//		const double errWorld = (double)lod->ErrorWorld;
//		if (errWorld <= 0.0)
//		{
//			// 误差为 0（通常是 Level 0），一定满足阈值，先记录下来
//			chosenLevel = lvl;
//			continue;
//		}
//
//		// 误差在屏幕空间的估计：相当于“误差占对角线世界长度的比例 × 像素对角线”
//		const double ssePx = (errWorld / diagWorld) * diagPx;
//
//		if (ssePx <= thresholdPx)
//		{
//			// 满足误差阈值，可以接受 → 继续向更粗的 level 尝试
//			chosenLevel = lvl;
//			foundWithinThresh = true;
//		}
//		else
//		{
//			// 进一步变粗后误差只会更大，可以直接停止
//			break;
//		}
//	}
//
//	// 如果所有 level 的误差都 > 阈值，则 chosenLevel 保持最细
//	// （即 foundWithinThresh == false 且 chosenLevel == validLevels.front()）
//	return chosenLevel;
//}

/**
 * @brief 执行LOD选择算法，确定每个tile节点应显示的LOD级别
 *
 * @details
 * 该方法实现了一个预算驱动的LOD选择系统，主要流程包括：
 * 1. 初始化阶段：
 *    - 清空当前活动节点列表(m_activeNow)
 *    - 重置运行时统计信息(m_rt)
 *    - 初始化优先级队列(Q)，按屏幕像素大小排序
 *
 * 2. 处理阶段：
 *    - 从队列中取出节点(NodeItem)
 *    - 使用chooseRepIdx_选择最佳LOD级别
 *    - 通过Cloud_BuildRepIfMissing确保LOD数据已构建
 *    - 根据预算(m_budget.maxPoints)和细化需求决定：
 *      * 显示当前LOD(加入m_activeNow)
 *      * 或继续细化(将子节点加入队列)
 *
 * 3. 统计阶段：
 *    - 更新已选择点数统计(m_rt.pointsChosen)
 *    - 更新显示节点数统计(m_rt.nodesShown)
 *
 */
void CloudLodController::selectLOD_()
{
	m_activeNow.clear();
	m_rt.pointsChosen = 0;
	m_rt.nodesShown = 0;

	std::priority_queue<NodeItem, std::vector<NodeItem>, ItemGreater> Q;

	// 1) 所有云的“根”入队
	for (auto& ce : m_clouds) {
		for (auto r : ce.roots) {
			if (!r) continue;
			double px = LeafProjector::PixelDiag(m_view, TL_Box(*r), 0);// m_budget.preloadHaloPx);
			Q.push(NodeItem{ r, px, 0 });
		}
	}

	const int budget = m_budget.maxPoints;

	int i = 0;
	//	printf("pixDiag:\n");

		// 2) 预算驱动的展开
	while (!Q.empty())
	{
		NodeItem it = Q.top(); Q.pop();
		auto& node = *it.node;
		if (it.pixDiag <= 0.5)
			continue; // 太小，无需显示

		// 打印每个pixDiag
//		std::cout << "(" << i++ << ", " << it.pixDiag << "), ";

		int repIdx = chooseRepIdx_(node, it.pixDiag, m_th);
		if (repIdx < 0)
			continue;

		auto& reps = TL_Reps(node);
		const int strideWant = reps.empty() ? 1 : reps[repIdx].stride;

		// 懒构建：遍历所有云，找到拥有该 node 的那一个 cloud 来构建/显示
		for (auto& ce : m_clouds) {
			// 这里假设 node 属于 ce.cloud；如果你有判断方法请替换
			Cloud_BuildRepIfMissing(ce.cloud, node, strideWant);
		}

		// 重新获取 reps（构建后已经具备）
		const auto& R = TL_Reps(node);
		if (repIdx >= (int)R.size()) repIdx = (int)R.size() - 1;
		int cost = R[repIdx].pointCount;

		// 是否需要细化？
		bool needRefine = false;
		if (it.pixDiag >= m_th.pixDiagFine) {
			needRefine = true; // 屏上“很大”，优先细化
		}
		else if (m_rt.pointsChosen + cost > budget) {
			needRefine = false; // 预算吃紧：就地用更粗 rep（本分支里保持当前 rep）
		}
		else {
			// 中间带 + 滞回（可按你的 TileLeaf 记录上帧 idx）
			// 这里简化：保持当前选择，不主动细化
			needRefine = false;
		}

		if (!needRefine || TL_IsLeaf(node)) {
			// 预算检查，不够则尝试更粗一档
			if (m_rt.pointsChosen + cost > budget && repIdx < (int)R.size() - 1) {
				int coarser = repIdx + 1;
				cost = R[coarser].pointCount;
				if (m_rt.pointsChosen + cost <= budget) repIdx = coarser;
			}
			if (m_rt.pointsChosen + cost <= budget) {
				m_activeNow.push_back(NodeRep{ &node, repIdx });
				m_rt.pointsChosen += cost;
				++m_rt.nodesShown;
			}
		}
		else {
			// 继续细化：推入子节点
			for (auto ch : TL_Children(node)) {
				if (!ch) continue;
				double px = LeafProjector::PixelDiag(m_view, TL_Box(*ch), 0);// m_budget.preloadHaloPx);
				Q.push(NodeItem{ ch, px, it.depth + 1 });
			}
		}
	}

	//	printf("pixDiag:\n");
}

/**
 * @brief 应用LOD选择结果，更新点云显示状态
 *
 * @details
 * 该方法比较当前帧(m_activeNow)和上一帧(m_activeLast)的LOD选择结果，
 * 执行必要的显示/隐藏操作：
 * 1. 对于上一帧显示但当前帧不需要的节点，调用Cloud_HideNodeRep隐藏
 * 2. 对于当前帧需要但上一帧未显示的节点，调用Cloud_ShowNodeRep显示
 *
 * 算法特点：
 * - 使用哈希集合快速比较两帧差异
 * - 最小化状态变更，只更新实际变化的节点
 * - 通过anyChanged标志返回是否有实际变更
 *
 * @return bool 返回true表示有节点显示状态发生改变，false表示无变化
 * @see CloudLodController::selectLOD_
 */
bool CloudLodController::applyDiff_()
{
	bool anyChanged = false;

	auto makeKey = [](const NodeRep& nr)->std::uintptr_t {
		return (std::uintptr_t)nr.node
			^ (std::uintptr_t)(nr.repIdx * 0x9e3779b97f4a7c15ull);
		};

	std::unordered_set<std::uintptr_t> lastSet;
	lastSet.reserve(m_activeLast.size() * 2 + 1);
	for (const NodeRep& nr : m_activeLast) lastSet.insert(makeKey(nr));

	std::unordered_set<std::uintptr_t> nowSet;
	nowSet.reserve(m_activeNow.size() * 2 + 1);
	for (const NodeRep& nr : m_activeNow)  nowSet.insert(makeKey(nr));

	// 1) 隐藏 last - now
	for (const NodeRep& nr : m_activeLast) {
		if (nowSet.find(makeKey(nr)) == nowSet.end()) {
			for (auto& ce : m_clouds) {
				if (!ce.cloud.IsNull()) {
					Cloud_HideNodeRep(ce.cloud, *nr.node, nr.repIdx);
					anyChanged = true;
					m_ctx->Redisplay(ce.cloud, false);
				}
			}
		}
	}

	// 2) 显示 now - last
	for (const NodeRep& nr : m_activeNow) {
		if (lastSet.find(makeKey(nr)) == lastSet.end()) {
			for (auto& ce : m_clouds) {
				if (!ce.cloud.IsNull()) {
					Cloud_BuildRepIfMissing(ce.cloud, *nr.node, nr.repIdx);
					Cloud_ShowNodeRep(ce.cloud, *nr.node, nr.repIdx);

					m_ctx->Redisplay(ce.cloud, false);
					anyChanged = true;
				}
			}
		}
	}

	m_activeLast.swap(m_activeNow);
	return anyChanged;
}